/**
* Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
/*
* Created on Oct 25, 2004
*
* @author Fabio Zadrozny
*/
package org.python.pydev.builder.pylint;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.ui.console.IOConsoleOutputStream;
import org.python.pydev.builder.PyDevBuilderVisitor;
import org.python.pydev.builder.PydevMarkerUtils;
import org.python.pydev.builder.PydevMarkerUtils.MarkerInfo;
import org.python.pydev.consoles.MessageConsoles;
import org.python.pydev.core.IInterpreterManager;
import org.python.pydev.core.MisconfigurationException;
import org.python.pydev.core.PythonNatureWithoutProjectException;
import org.python.pydev.core.callbacks.ICallback0;
import org.python.pydev.core.docutils.StringUtils;
import org.python.pydev.core.log.Log;
import org.python.pydev.plugin.PydevPlugin;
import org.python.pydev.plugin.nature.PythonNature;
import org.python.pydev.runners.SimplePythonRunner;
import org.python.pydev.ui.UIConstants;
import com.aptana.shared_core.io.FileUtils;
import com.aptana.shared_core.structure.Tuple;
/**
*
* Check lint.py for options.
*
* @author Fabio Zadrozny
*/
public class PyLintVisitor extends PyDevBuilderVisitor {
/* (non-Javadoc)
* @see org.python.pydev.builder.PyDevBuilderVisitor#visitResource(org.eclipse.core.resources.IResource)
*/
public static final String PYLINT_PROBLEM_MARKER = "org.python.pydev.pylintproblemmarker";
public static final List<PyLintThread> pyLintThreads = new ArrayList<PyLintThread>();
private static Object lock = new Object();
/**
* This class runs as a thread to get the markers, and only stops the IDE when the markers are being added.
*
* @author Fabio Zadrozny
*/
public static class PyLintThread extends Thread {
IResource resource;
ICallback0<IDocument> document;
IPath location;
List<Object[]> markers = new ArrayList<Object[]>();
public PyLintThread(IResource resource, ICallback0<IDocument> document, IPath location) {
setName("PyLint thread");
this.resource = resource;
this.document = document;
this.location = location;
}
/**
* @return
*/
private boolean canPassPyLint() {
if (pyLintThreads.size() < PyLintPrefPage.getMaxPyLintDelta()) {
pyLintThreads.add(this);
return true;
}
return false;
}
/**
* @see java.lang.Thread#run()
*/
public void run() {
try {
if (canPassPyLint()) {
IOConsoleOutputStream out = getConsoleOutputStream();
final IDocument doc = document.call();
passPyLint(resource, out, doc);
new Job("Adding markers") {
protected IStatus run(IProgressMonitor monitor) {
ArrayList<MarkerInfo> lst = new ArrayList<PydevMarkerUtils.MarkerInfo>();
for (Iterator<Object[]> iter = markers.iterator(); iter.hasNext();) {
Object[] el = iter.next();
String tok = (String) el[0];
int priority = ((Integer) el[1]).intValue();
String id = (String) el[2];
int line = ((Integer) el[3]).intValue();
lst.add(new PydevMarkerUtils.MarkerInfo(doc, "ID:" + id + " " + tok,
PYLINT_PROBLEM_MARKER, priority, false, false, line, 0, line, 0, null));
}
PydevMarkerUtils.replaceMarkers(lst, resource, PYLINT_PROBLEM_MARKER, true, monitor);
return PydevPlugin.makeStatus(Status.OK, "", null);
}
}.schedule();
}
} catch (final Exception e) {
new Job("Error reporting") {
protected IStatus run(IProgressMonitor monitor) {
Log.log(e);
return PydevPlugin.makeStatus(Status.OK, "", null);
}
}.schedule();
} finally {
try {
pyLintThreads.remove(this);
} catch (Exception e) {
Log.log(e);
}
}
}
private IOConsoleOutputStream getConsoleOutputStream() throws MalformedURLException {
if (PyLintPrefPage.useConsole()) {
return MessageConsoles.getConsoleOutputStream("PyLint", UIConstants.PY_LINT_ICON);
} else {
return null;
}
}
/**
* @param tok
* @param type
* @param priority
* @param id
* @param line
*/
private void addToMarkers(String tok, int priority, String id, int line) {
markers.add(new Object[] { tok, priority, id, line });
}
/**
* @param resource
* @param out
* @param doc
* @param document
* @param location
* @throws CoreException
* @throws MisconfigurationException
* @throws PythonNatureWithoutProjectException
*/
private void passPyLint(IResource resource, IOConsoleOutputStream out, IDocument doc) throws CoreException,
MisconfigurationException, PythonNatureWithoutProjectException {
File script = new File(PyLintPrefPage.getPyLintLocation());
File arg = new File(location.toOSString());
ArrayList<String> list = new ArrayList<String>();
list.add("--include-ids=y");
//user args
String userArgs = StringUtils.replaceNewLines(PyLintPrefPage.getPyLintArgs(), " ");
StringTokenizer tokenizer2 = new StringTokenizer(userArgs);
while (tokenizer2.hasMoreTokens()) {
list.add(tokenizer2.nextToken());
}
list.add(FileUtils.getFileAbsolutePath(arg));
IProject project = resource.getProject();
String scriptToExe = FileUtils.getFileAbsolutePath(script);
String[] paramsToExe = list.toArray(new String[0]);
write("PyLint: Executing command line:'", out, scriptToExe, paramsToExe, "'");
PythonNature nature = PythonNature.getPythonNature(project);
if (nature == null) {
Throwable e = new RuntimeException("PyLint ERROR: Nature not configured for: " + project);
Log.log(e);
return;
}
Tuple<String, String> outTup = new SimplePythonRunner().runAndGetOutputFromPythonScript(nature
.getProjectInterpreter().getExecutableOrJar(), scriptToExe, paramsToExe, arg.getParentFile(),
project);
write("PyLint: The stdout of the command line is: " + outTup.o1, out);
write("PyLint: The stderr of the command line is: " + outTup.o2, out);
String output = outTup.o1;
StringTokenizer tokenizer = new StringTokenizer(output, "\r\n");
boolean useW = PyLintPrefPage.useWarnings();
boolean useE = PyLintPrefPage.useErrors();
boolean useF = PyLintPrefPage.useFatal();
boolean useC = PyLintPrefPage.useCodingStandard();
boolean useR = PyLintPrefPage.useRefactorTips();
//Set up local values for severity
int wSeverity = PyLintPrefPage.wSeverity();
int eSeverity = PyLintPrefPage.eSeverity();
int fSeverity = PyLintPrefPage.fSeverity();
int cSeverity = PyLintPrefPage.cSeverity();
int rSeverity = PyLintPrefPage.rSeverity();
//System.out.println(output);
if (output.indexOf("Traceback (most recent call last):") != -1) {
Throwable e = new RuntimeException("PyLint ERROR: \n" + output);
Log.log(e);
return;
}
if (outTup.o2.indexOf("Traceback (most recent call last):") != -1) {
Throwable e = new RuntimeException("PyLint ERROR: \n" + outTup.o2);
Log.log(e);
return;
}
while (tokenizer.hasMoreTokens()) {
String tok = tokenizer.nextToken();
try {
boolean found = false;
int priority = 0;
//W0611: 3: Unused import finalize
//F0001: 0: Unable to load module test.test2 (list index out of range)
//C0321: 25:fdfd: More than one statement on a single line
int indexOfDoublePoints = tok.indexOf(":");
if (indexOfDoublePoints != -1) {
if (tok.startsWith("C") && useC) {
found = true;
//priority = IMarker.SEVERITY_WARNING;
priority = cSeverity;
} else if (tok.startsWith("R") && useR) {
found = true;
//priority = IMarker.SEVERITY_WARNING;
priority = rSeverity;
} else if (tok.startsWith("W") && useW) {
found = true;
//priority = IMarker.SEVERITY_WARNING;
priority = wSeverity;
} else if (tok.startsWith("E") && useE) {
found = true;
//priority = IMarker.SEVERITY_ERROR;
priority = eSeverity;
} else if (tok.startsWith("F") && useF) {
found = true;
//priority = IMarker.SEVERITY_ERROR;
priority = fSeverity;
} else {
continue;
}
} else {
continue;
}
try {
if (found) {
String id = tok.substring(0, tok.indexOf(":")).trim();
int i = tok.indexOf(":");
if (i == -1)
continue;
tok = tok.substring(i + 1);
i = tok.indexOf(":");
if (i == -1)
continue;
final String substring = tok.substring(0, i).trim();
//On PyLint 0.24 it started giving line,col (and not only the line).
int line = Integer.parseInt(StringUtils.split(substring, ',').get(0));
IRegion region = null;
try {
region = doc.getLineInformation(line - 1);
} catch (Exception e) {
region = doc.getLineInformation(line);
}
String lineContents = doc.get(region.getOffset(), region.getLength());
int pos = -1;
if ((pos = lineContents.indexOf("IGNORE:")) != -1) {
String lintW = lineContents.substring(pos + "IGNORE:".length());
if (lintW.startsWith(id)) {
continue;
}
}
i = tok.indexOf(":");
if (i == -1)
continue;
tok = tok.substring(i + 1);
addToMarkers(tok, priority, id, line - 1);
}
} catch (RuntimeException e2) {
Log.log(e2);
}
} catch (Exception e1) {
Log.log(e1);
}
}
}
}
@Override
public void visitChangedResource(IResource resource, ICallback0<IDocument> document, IProgressMonitor monitor) {
if (document == null) {
return;
}
//Whenever PyLint is passed, the markers will be deleted.
try {
resource.deleteMarkers(PYLINT_PROBLEM_MARKER, false, IResource.DEPTH_ZERO);
} catch (CoreException e3) {
Log.log(e3);
}
if (PyLintPrefPage.usePyLint() == false) {
return;
}
IProject project = resource.getProject();
PythonNature pythonNature = PythonNature.getPythonNature(project);
try {
//pylint can only be used for jython projects
if (pythonNature.getInterpreterType() != IInterpreterManager.INTERPRETER_TYPE_PYTHON) {
return;
}
//must be in a source folder (not external)
if (!isResourceInPythonpathProjectSources(resource, pythonNature, false)) {
return;
}
} catch (Exception e) {
return;
}
if (project != null && resource instanceof IFile) {
IFile file = (IFile) resource;
IPath location = file.getRawLocation();
if (location != null) {
PyLintThread thread = new PyLintThread(resource, document, location);
thread.start();
}
}
}
public static void write(String cmdLineToExe, IOConsoleOutputStream out, Object... args) {
try {
if (out != null) {
synchronized (lock) {
if (args != null) {
for (Object arg : args) {
if (arg instanceof String) {
cmdLineToExe += " " + arg;
} else if (arg instanceof String[]) {
String[] strings = (String[]) arg;
for (String string : strings) {
cmdLineToExe += " " + string;
}
}
}
}
out.write(cmdLineToExe);
}
}
} catch (IOException e) {
Log.log(e);
}
}
@Override
public void visitRemovedResource(IResource resource, ICallback0<IDocument> document, IProgressMonitor monitor) {
}
/**
* @see org.python.pydev.builder.PyDevBuilderVisitor#maxResourcesToVisit()
*/
public int maxResourcesToVisit() {
int i = PyLintPrefPage.getMaxPyLintDelta();
if (i < 0) {
i = 0;
}
return i;
}
}